/*
    SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QSignalSpy>
#include <QTest>
#include <QThread>
// client
#include "../../src/client/connection_thread.h"
#include "../../src/client/event_queue.h"
#include "../../src/client/idle.h"
#include "../../src/client/registry.h"
#include "../../src/client/seat.h"
// server
#include "../../src/server/display.h"
#include "../../src/server/idle_interface.h"
#include "../../src/server/seat_interface.h"

using namespace KWayland::Client;
using namespace KWayland::Server;

class IdleTest : public QObject
{
    Q_OBJECT
private Q_SLOTS:
    void init();
    void cleanup();

    void testTimeout();
    void testSimulateUserActivity();
    void testServerSimulateUserActivity();
    void testIdleInhibit();
    void testIdleInhibitBlocksTimeout();

private:
    Display *m_display = nullptr;
    SeatInterface *m_seatInterface = nullptr;
    IdleInterface *m_idleInterface = nullptr;
    ConnectionThread *m_connection = nullptr;
    QThread *m_thread = nullptr;
    EventQueue *m_queue = nullptr;
    Seat *m_seat = nullptr;
    Idle *m_idle = nullptr;
};

static const QString s_socketName = QStringLiteral("kwayland-test-idle-0");

void IdleTest::init()
{
    delete m_display;
    m_display = new Display(this);
    m_display->setSocketName(s_socketName);
    m_display->start();
    QVERIFY(m_display->isRunning());
    m_display->createShm();
    m_seatInterface = m_display->createSeat();
    m_seatInterface->setName(QStringLiteral("seat0"));
    m_seatInterface->create();
    m_idleInterface = m_display->createIdle();
    m_idleInterface->create();

    // setup connection
    m_connection = new KWayland::Client::ConnectionThread;
    QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
    QVERIFY(connectedSpy.isValid());
    m_connection->setSocketName(s_socketName);

    m_thread = new QThread(this);
    m_connection->moveToThread(m_thread);
    m_thread->start();

    m_connection->initConnection();
    QVERIFY(connectedSpy.wait());

    m_queue = new EventQueue(this);
    m_queue->setup(m_connection);

    Registry registry;
    QSignalSpy interfacesAnnouncedSpy(&registry, &Registry::interfacesAnnounced);
    QVERIFY(interfacesAnnouncedSpy.isValid());
    registry.setEventQueue(m_queue);
    registry.create(m_connection);
    QVERIFY(registry.isValid());
    registry.setup();
    QVERIFY(interfacesAnnouncedSpy.wait());

    m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name,
                                 registry.interface(Registry::Interface::Seat).version,
                                 this);
    QVERIFY(m_seat->isValid());
    m_idle = registry.createIdle(registry.interface(Registry::Interface::Idle).name,
                                 registry.interface(Registry::Interface::Idle).version,
                                 this);
    QVERIFY(m_idle->isValid());
}

void IdleTest::cleanup()
{
#define CLEANUP(variable) \
    if (variable) { \
        delete variable; \
        variable = nullptr; \
    }
    CLEANUP(m_idle)
    CLEANUP(m_seat)
    CLEANUP(m_queue)
    if (m_connection) {
        m_connection->deleteLater();
        m_connection = nullptr;
    }
    if (m_thread) {
        m_thread->quit();
        m_thread->wait();
        delete m_thread;
        m_thread = nullptr;
    }

    CLEANUP(m_idleInterface)
    CLEANUP(m_seatInterface)
    CLEANUP(m_display)
#undef CLEANUP
}

void IdleTest::testTimeout()
{
    // this test verifies the basic functionality of a timeout, that it gets fired
    // and that it resumes from idle, etc.
    QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(1, m_seat));
    QVERIFY(timeout->isValid());
    QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
    QVERIFY(idleSpy.isValid());
    QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
    QVERIFY(resumedFormIdleSpy.isValid());

    // we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
    QVERIFY(!idleSpy.wait(500));
    // the default of 5 sec will now pass
    QVERIFY(idleSpy.wait());

    // simulate some activity
    QVERIFY(resumedFormIdleSpy.isEmpty());
    m_seatInterface->setTimestamp(1);
    QVERIFY(resumedFormIdleSpy.wait());

    timeout.reset();
    m_connection->flush();
    m_display->dispatchEvents();
}

void IdleTest::testSimulateUserActivity()
{
    // this test verifies that simulate user activity doesn't fire the timer
    QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(6000, m_seat));
    QVERIFY(timeout->isValid());
    QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
    QVERIFY(idleSpy.isValid());
    QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
    QVERIFY(resumedFormIdleSpy.isValid());
    m_connection->flush();

    QTest::qWait(4000);
    timeout->simulateUserActivity();
    // waiting default five sec should fail
    QVERIFY(!idleSpy.wait());
    // another 2 sec should fire
    QVERIFY(idleSpy.wait(2000));

    // now simulating user activity should emit a resumedFromIdle
    QVERIFY(resumedFormIdleSpy.isEmpty());
    timeout->simulateUserActivity();
    QVERIFY(resumedFormIdleSpy.wait());

    timeout.reset();
    m_connection->flush();
    m_display->dispatchEvents();
}

void IdleTest::testServerSimulateUserActivity()
{
    // this test verifies that simulate user activity doesn't fire the timer
    QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(6000, m_seat));
    QVERIFY(timeout->isValid());
    QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
    QVERIFY(idleSpy.isValid());
    QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
    QVERIFY(resumedFormIdleSpy.isValid());
    m_connection->flush();

    QTest::qWait(4000);
    m_idleInterface->simulateUserActivity();
    // waiting default five sec should fail
    QVERIFY(!idleSpy.wait());
    // another 2 sec should fire
    QVERIFY(idleSpy.wait(2000));

    // now simulating user activity should emit a resumedFromIdle
    QVERIFY(resumedFormIdleSpy.isEmpty());
    m_idleInterface->simulateUserActivity();
    QVERIFY(resumedFormIdleSpy.wait());

    timeout.reset();
    m_connection->flush();
    m_display->dispatchEvents();
}

void IdleTest::testIdleInhibit()
{
    QCOMPARE(m_idleInterface->isInhibited(), false);
    QSignalSpy idleInhibitedSpy(m_idleInterface, &IdleInterface::inhibitedChanged);
    QVERIFY(idleInhibitedSpy.isValid());
    m_idleInterface->inhibit();
    QCOMPARE(m_idleInterface->isInhibited(), true);
    QCOMPARE(idleInhibitedSpy.count(), 1);
    m_idleInterface->inhibit();
    QCOMPARE(m_idleInterface->isInhibited(), true);
    QCOMPARE(idleInhibitedSpy.count(), 1);
    m_idleInterface->uninhibit();
    QCOMPARE(m_idleInterface->isInhibited(), true);
    QCOMPARE(idleInhibitedSpy.count(), 1);
    m_idleInterface->uninhibit();
    QCOMPARE(m_idleInterface->isInhibited(), false);
    QCOMPARE(idleInhibitedSpy.count(), 2);
}

void IdleTest::testIdleInhibitBlocksTimeout()
{
    // this test verifies that a timeout does not fire when the system is inhibited

    // so first inhibit
    QCOMPARE(m_idleInterface->isInhibited(), false);
    m_idleInterface->inhibit();

    QScopedPointer<IdleTimeout> timeout(m_idle->getTimeout(1, m_seat));
    QVERIFY(timeout->isValid());
    QSignalSpy idleSpy(timeout.data(), &IdleTimeout::idle);
    QVERIFY(idleSpy.isValid());
    QSignalSpy resumedFormIdleSpy(timeout.data(), &IdleTimeout::resumeFromIdle);
    QVERIFY(resumedFormIdleSpy.isValid());

    // we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
    QVERIFY(!idleSpy.wait(500));
    // the default of 5 sec won't pass
    QVERIFY(!idleSpy.wait());

    // simulate some activity
    QVERIFY(resumedFormIdleSpy.isEmpty());
    m_seatInterface->setTimestamp(1);
    // resume from idle should not fire
    QVERIFY(!resumedFormIdleSpy.wait());

    // let's uninhibit
    m_idleInterface->uninhibit();
    QCOMPARE(m_idleInterface->isInhibited(), false);
    // we requested a timeout of 1 msec, but the minimum the server sets is 5 sec
    QVERIFY(!idleSpy.wait(500));
    // the default of 5 sec will now pass
    QVERIFY(idleSpy.wait());

    // if we inhibit now it will trigger a resume from idle
    QVERIFY(resumedFormIdleSpy.isEmpty());
    m_idleInterface->inhibit();
    QVERIFY(resumedFormIdleSpy.wait());

    // let's wait again just to verify that also inhibit for already existing IdleTimeout works
    QVERIFY(!idleSpy.wait(500));
    QVERIFY(!idleSpy.wait());
    QCOMPARE(idleSpy.count(), 1);

    timeout.reset();
    m_connection->flush();
    m_display->dispatchEvents();
}

QTEST_GUILESS_MAIN(IdleTest)
#include "test_idle.moc"
